<?php
/**
 * Constructs model of a data table using separate object for each column.
 * Can be writen as HTML using write() method. 
 * Methods of column objects usually return itself so that you can chain
 * method calls like $object->required()->style('').
 * @author jan
 */
class EntityListBuilder {

    private $fieldList = array();
    private $pkField;
    private $entityName;
    private $entityList;
    private $controller;
    private $rowCssClassFunction;
    private $displayMethodPath;
    private $extraClassNames;
    private $uuid; // for sorting

    public function __construct($controllerOrEntityName, $entityList, $pkField = 'id') {
        // parent::__construct();
        if (is_string($controllerOrEntityName)) {
            $this->entityName = $controllerOrEntityName;
        }
        else {
            $this->controller = $controllerOrEntityName;
            $this->entityName = $this->controller->entityName;
        }
        $this->entityList = $entityList;
        $this->pkField = $pkField;

        $this->displayMethodPath = Router::$segments[0] . '/' . Router::$controller . '/display';

        $this->uuid = $uuid;
        if (! $this->uuid) {
            if ($this->controller) {
                $this->uuid = $this->controller->formName;
            }
        }
    }
    
    public function setEntityList($list) {
    	$this->entityList = $list;
    }

    public function setUuid($uuid) {
        $this->uuid = $uuid;
    }

    public function setDisplayMethodPath($displayController) {
        $this->displayMethodPath = $displayController;
    }

    public function rowCssClassFunction($function) {
        $this->rowCssClassFunction = $function;
    }

    public function extraClassNames($classNames) {
        $this->extraClassNames = $classNames;
        return $this;
    }

    public function setId($id) {
        $this->tableId = $id;
        return $this;
    }

    public function write() {
        $sessionData = SessionData::instance();
        $sortOrder = $sessionData->getListOrder($this->uuid);

        if ($this->controller) {
            if ($this->controller->pagination && $this->controller->pagination->enabled) {
                echo '<p class="tableItemCount">';
                echo text::getEscaped('pagination.totalCount') . ': ' . $this->controller->pagination->total_items;
                if ($this->controller->pagination->total_pages > 1) {
                    echo ' (' . $this->controller->pagination->items_per_page . ' ' . text::getEscaped('pagination.perPage') . ')';
                }
                echo '</p>';
            }
            else {
	            echo '<p class="tableItemCount">';
	            echo text::getEscaped('pagination.totalCount') . ': ' . count($this->entityList);
	            echo '</p>';
            }
            
            echo $this->controller->pagination->render();
        }
        echo '<table ' . ($this->tableId ? 'id="' . $this->tableId . '"' : '') . ' class="listTable ' . $this->extraClassNames . '">';
        echo '<thead>';
        echo '<tr>';
        foreach ($this->fieldList as $field) {
            html::listHeader($field->getOrderByField(), $this->entityName, $sortOrder, $this->uuid, $field->getLabelKey());
        }
        echo '</tr>';
        echo '</thead>';
        echo '<tbody>';
        foreach ($this->entityList as $entity) {
            $rowClass = '';
            if ($this->rowCssClassFunction) {
                $function = $this->rowCssClassFunction;
                echo '<tr class="' . $function($entity) . '">';
            }
            else {
                echo '<tr>';
            }
            
            for ($i = 0; $i < count($this->fieldList); $i++) {
                $field = $this->fieldList[$i];
                echo '<td class="' . $field->fieldCssClass() . '">' . $this->computeFieldValue($entity, $field, $i) . '</td>';
            }
            echo '</tr>';
        }
        echo '</tbody>';
        echo '</table>';
        if ($this->controller) {
            echo $this->controller->pagination->render();
        }
    }
    
    private function computeFieldValue($entity, $field, $index) {
        $displayUrl = NULL;

        $valueFunction = $field->getValueFunctionName();
        if ($valueFunction) {
        	
        	$params = array($entity, $field);
        	$extraParams = $field->getValueFunctionArgs();
        	if ($extraParams) {
        		foreach ($extraParams as $value) {
        			$params[] = $value;
        		}
        	}
        	
        	if (strpos($valueFunction, '::')) {
        		// If $valueFunction contains name of a static function, it must be transformed into array
        		$valueFunction = preg_split('/::/', $valueFunction);
        	}
        	$fieldValue = call_user_func_array($valueFunction, $params);
        }
        else {
            $fieldValue = $field->fieldValue($entity);
        }

        if ($field->isNoLink()) {
            return $fieldValue;
        }

        if ($fieldValue && $field->getLinkUrl()) {
            $sprintfParams = array($field->getLinkUrl());
            foreach ($field->getLinkValueField() as $entityFieldName) {
                $sprintfParams[] = $entity[$entityFieldName];
            }
            // Stupid function sprintf does not accept array of parameters :(
            $displayUrl = call_user_func_array('sprintf', $sprintfParams);
        }

        if ($index == 0) {
            if (! $displayUrl) {
                $displayUrl = $this->displayMethodPath . '?' . $this->pkField . '=' . $entity[$this->pkField];
            }

            if (! $fieldValue) {
                $fieldValue = '&ndash;';
            }
            if ($field->getOpenInPopup()) {
                $fieldValue = html::moreInfoPopup($displayUrl, $fieldValue);
            }
            else {
                $fieldValue = html::anchor($displayUrl, $fieldValue);
            }
        }
        else {
            if ($fieldValue && $field->getLinkUrl()) {
                $valueFieldArray = $field->getLinkValueField();
                $fieldValue = html::moreInfoPopup($displayUrl , $fieldValue);
            }
        }
        return $fieldValue;
    }

    public function add($field) {
        $this->fieldList[] = $field;
        return $field;
    }

    /** @return TextListField */
    public function text($fieldName) {
        return $this->add(new TextListField($this->entityName, $fieldName));
    }
    
    /** @return TextListField */
    public function price($fieldName, $currencyFieldName) {
        return $this->add(new PriceListField($this->entityName, $fieldName, $currencyFieldName));
    }
    
    /** @return BooleanListField */
    public function boolean($fieldName) {
        return $this->add(new BooleanListField($this->entityName, $fieldName));
    }

    /** @return IntegerListField */
    public function integer($fieldName) {
        return $this->add(new IntegerListField($this->entityName, $fieldName));
    }

    /** @return DecimalListField */
    public function decimal($fieldName) {
        return $this->add(new DecimalListField($this->entityName, $fieldName));
    }

    /** @return TimestampListField */
    public function timestamp($fieldName) {
        return $this->add(new TimestampListField($this->entityName, $fieldName));
    }

    /** @return DateListField */
    public function date($fieldName) {
        return $this->add(new DateListField($this->entityName, $fieldName));
    }
    
    /** @return TimeListField */
    public function time($fieldName) {
        return $this->add(new TimeListField($this->entityName, $fieldName));
    }

    /** @return LongTextListField */
    public function longText($fieldName) {
        return $this->add(new LongTextListField($this->entityName, $fieldName));
    }
    
    /** @return CheckboxField */
    public function checkbox($fieldName, $valueFieldName) {
        return $this->add(new CheckboxField($this->entityName, $fieldName, $valueFieldName));
    }
    
    /** @return ComboBoxField */
    public function combobox($fieldName, $options, $attributes=null) {
        return $this->add(new ComboboxField($this->entityName, $fieldName, $options, $attributes));
    }
    
    /** @return AutocompleteField */
    public function autocomplete($fieldName, $options, $attributes=null) {
    	return $this->add(new AutocompleteField($this->entityName, $fieldName, $options, $attributes));
    }
}

abstract class EntityListField {
    protected $labelKey;
    protected $fieldName;
    protected $cssClass = '';
    protected $valueL10nPrefixKey;
    protected $skipRowsWithEmptyValues;
    protected $entityName;
    protected $orderByField;
    protected $linkUrl;
    protected $linkValueField;
    protected $valueFunctionName;
    protected $valueFunctionArgs;
    protected $noLink = FALSE;
    protected $openInPopup = FALSE;

    public function __construct($entityName, $fieldName) {
        // parent::__construct();
        $this->fieldName = $fieldName;
        $this->entityName = $entityName;
        $this->labelKey = $entityName . '.' . $fieldName;
    }

    public function getFieldName() {
        return $this->fieldName;
    }
    
    public function getLabelKey() {
        return $this->labelKey;
    }

    /** @return EntityListField */
    public function labelKey($labelKey) {
        if ($labelKey) {
            if (strpos($labelKey, '.') === FALSE) {
                $labelKey = $this->entityName . '.' . $labelKey;
            }
        }
        $this->labelKey = $labelKey;
        return $this;
    }

    /** @return EntityListField */
    public function alignRight() {
        $this->cssClass .= ' textRight';
        return $this;
    }

    /** @return EntityListField */
    public function alignLeft() {
        $this->cssClass .= ' textLeft';
        return $this;
    }
    
    public function openInPopup($inPopup=TRUE) {
    	$this->openInPopup = $inPopup;
    	return $this;
    }
    
    public function getOpenInPopup() {
    	return $this->openInPopup;
    }

    /** @return EntityListField */
    public function cssClass($cssClass) {
        $this->cssClass .= ' ' . $cssClass;
        return $this;
    }

    /** @return EntityListField */
    public function l10n($valueL10nPrefixKey, $skipRowsWithEmptyValues=FALSE) {
        $this->valueL10nPrefixKey = $valueL10nPrefixKey;
        $this->skipRowsWithEmptyValues = $skipRowsWithEmptyValues;
        return $this;
    }

    /*public function fieldLabel() {
        return text::get($this->labelKey);
    }*/

    public function fieldValue($entity) {
    	$value = $entity[$this->fieldName];
        if ((! $value && ! $this->skipRowsWithEmptyValues || $value) && $this->valueL10nPrefixKey) {
            return html::escape(text::get($this->valueL10nPrefixKey . $value));
        }
        return html::escape($value);
    }

    public function fieldCssClass() {
        return trim($this->cssClass);
    }

    public function orderByField($field) {
        $this->orderByField = $field;
        return $this;
    }

    public function getOrderByField() {
        if ($this->orderByField) {
            return $this->orderByField;
        }
        return $this->fieldName;
    }

    public function getLinkUrl() {
        return $this->linkUrl;
    }

    public function getLinkValueField() {
        return $this->linkValueField;
    }

    public function linkWithValue($url, $linkValueField=null) {
        $this->linkUrl = $url . '%s';
        $this->linkValueField = array($linkValueField ? $linkValueField : $this->fieldName);
        return $this;
    }

    public function linkWithParams($url, $params) {
        if (! is_array($params)) {
            $params = array($params);
        }
        $this->linkUrl = $url;
        $this->linkValueField = $params;
        return $this;
    }

    public function valueFunction($valueFunctionName) {
        $this->valueFunctionName = $valueFunctionName;
        $args = func_get_args();
        if (count($args) > 1) {
        	array_shift($args);
        	$this->valueFunctionArgs = $args;
        }
        return $this;
    }
    
    public function getValueFunctionName() {
        return $this->valueFunctionName;
    }
    
    public function getValueFunctionArgs() {
    	return $this->valueFunctionArgs;
    }

    public function noLink($value = true) {
        $this->noLink = $value;
    }

    public function isNoLink() {
        return $this->noLink;
    }
}

class TextListField extends EntityListField {
    private $valueFieldList;
    private $valueToTextMap;

    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    /**
     * For columns where you want to display two values, eg. first and last name.  
     * @return TextListField
     */
    public function doubleValue($firstValueField, $secondValueField) {
        $this->valueFieldList = array($firstValueField, $secondValueField);
        return $this;
    }
    
    /**
     * Used for mapping values from database to human readable texts using a map (option list).
     * For example, preferred language code from database using constants::$languageNames as the map. 
     * @return TextListField
     */
    public function valueToTextMap($valueToTextMap) {
    	$this->valueToTextMap = $valueToTextMap;
    	return $this;
    }
    
    public function fieldValue($entity) {
        if ($this->valueFieldList) {
            $value = '';
            foreach ($this->valueFieldList as $valueField) {
                $value .= $entity[$valueField] . ' ';
            }
            return trim($value);
        }
        if ($this->valueToTextMap) {
        	$value = $entity[$this->fieldName];
        	if ($value === null) {
        		return '';
        	}
        	return $this->valueToTextMap[$value];
        }
        return parent::fieldValue($entity);
    }
}

class PriceListField extends TextListField {
    private $currencyFieldName;

    public function __construct($entityName, $fieldName, $currencyFieldName) {
        parent::__construct($entityName, $fieldName);
        $this->currencyFieldName = $currencyFieldName;
    }

    public function fieldValue($entity) {
        $value = $entity[$this->fieldName];
        if ($value) {
            $currencyMark = $entity[$this->currencyFieldName];
            if ($currencyMark) {
                $currencyMark = text::get('currency.mark-' . $currencyMark);
            }
            return format::decimal($value) . '&nbsp;' . $currencyMark;
        }
        return '';
    }
}

class IntegerListField extends EntityListField {
    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }
}

class BooleanListField extends EntityListField {
    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    public function fieldValue($entity) {
        return format::boolean($entity[$this->fieldName]);
    }
}

class TimestampListField extends EntityListField {
    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    public function fieldValue($entity) {
        return format::timestamp($entity[$this->fieldName]);
    }
}

class DateListField extends EntityListField {
    private $yearOnly;
    
    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    public function fieldValue($entity) {
        $date = $entity[$this->fieldName];
        if ($date) {
            if ($this->yearOnly) {
                $date = format::year($entity[$this->fieldName]);
            }
            else {
                $date = format::date($entity[$this->fieldName]);
            }
        }
        return $date;
    }
    
    public function yearOnly() {
        $this->yearOnly = true;
        return $this;
    }
}

class TimeListField extends EntityListField {
    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    public function fieldValue($entity) {
        return format::time($entity[$this->fieldName]);
    }
}

class DecimalListField extends EntityListField {
    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    public function fieldValue($entity) {
        return format::decimal($entity[$this->fieldName]);
    }
}

class LongTextListField extends EntityListField {

    private $maxLength = 48;

    public function __construct($entityName, $fieldName) {
        parent::__construct($entityName, $fieldName);
    }

    public function fieldValue($entity) {
        $value = $entity[$this->fieldName];
        $shortValue = string::longTextShort($value, $this->maxLength);
        if ($value != $shortValue) {
        	return '<span title="' . Html::escape($value) . '">' . Html::escape($shortValue) . '</span>';
        }
        return $value;
    }
}

class CheckboxField extends EntityListField {
    
    private $valueFieldName;

    public function __construct($entityName, $fieldName, $valueFieldName) {
        parent::__construct($entityName, $fieldName);
        $this->valueFieldName = $valueFieldName;
    }
    
    public function getLabelKey() {
        return 'util-emptyString';
    }

    public function fieldValue($entity) {
        $value = $entity[$this->valueFieldName];
        return '<input type="checkbox" name="' . $this->fieldName . '" value="' . Html::escape($value) . '" />';
    }
}

class ComboboxField extends EntityListField {
    
    protected $options;
    protected $attributes;
    protected $nameSuffixFieldName;
    protected $emptyOption = TRUE;
    
    public function __construct($entityName, $fieldName, $options, $attributes) {
        parent::__construct($entityName, $fieldName);
        $this->options = $options;
        $this->attributes = $attributes;
    }
    
    public function fieldValue($entity) {
        $name = $this->fieldName;
        if ($this->nameSuffixFieldName) {
            $name .= '_' . $entity[$this->nameSuffixFieldName];
        }
        
        $options = $this->options;
        if ($this->emptyOption) {
        	$options = array_merge(array('' => ''), $this->options);
        }
        return form::dropdown($name, $options, $entity[$this->fieldName], $this->attributes);
    }
 
    /** @return ComboboxField */
    public function addNameSuffix($nameSuffixFieldName) {
        $this->nameSuffixFieldName = $nameSuffixFieldName;
        return $this;
    }
    
	/** @return ComboboxField */
    public function noEmptyOption() {
        $this->emptyOption = FALSE;
        return $this;
    }
}

class AutocompleteField extends ComboboxField {

	public function __construct($entityName, $fieldName, $options, $attributes) {
		parent::__construct($entityName, $fieldName, $options, $attributes);
	}

	public function fieldValue($entity) {
		$name = $this->fieldName;
		if ($this->nameSuffixFieldName) {
			$name .= '_' . $entity[$this->nameSuffixFieldName];
		}

		$options = $this->options;
		if ($this->emptyOption) {
			$options = array_merge(array('' => ''), $this->options);
		}
		$html = form::dropdown($name, $options, $entity[$this->fieldName], $this->attributes);
		// $html .= html::inlineJavaScript('$(document).ready(function() { Yosemin.Autocomplete.create(\'' . $name . '_text\', ' . javascript::toJsArray($options)  . ', \'' . $name . '\', ' . javascript::toAssociativeArray($options) . '); });');
		$html .= html::inlineJavaScript('$(document).ready(function() { $(document.getElementById(\'' . $name . '\')).combobox(); });');

		return $html;
	}
}
